#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>

#include "trace.h"

int base64_enc(const unsigned char *input, int length, char **out_buf, int *len_out_buf)
{
    int rc = 0;

    if((NULL == out_buf) || (NULL == len_out_buf)){
        TRACE_ERROR("Invalid input pointer parameters\n");
        return 1;
    }

    BIO *bmem;  // memory BIO
    bmem = BIO_new(BIO_s_mem());

    BIO *b64;   // base64 BIO
    b64 = BIO_new(BIO_f_base64());

    // append memory bio to base64 bio
    b64 = BIO_push(b64, bmem);

    BIO_write(b64, input, length);
    BIO_flush(b64);

    // extract the BUF_MEM structure from the BIO chain
    BUF_MEM *bptr;
    BIO_get_mem_ptr(b64, &bptr);

    *len_out_buf = bptr->length;

    // to be freed by the caller
    *out_buf = (char *)malloc(bptr->length);
    memcpy(*out_buf, bptr->data, bptr->length-1);
    (*out_buf)[bptr->length-1] = 0;

    // free up the BIO chain
    BIO_free_all(b64);

    return rc;
}

int base64_dec(unsigned char *input, int length, char **out_buf, int *len_out_buf)
{
    int rc = 0;

    if((NULL == out_buf) || (NULL == len_out_buf)){
        TRACE_ERROR("Invalid input pointer parameters\n");
        return 1;
    }

    BIO *b64;   // base64 BIO
    b64 = BIO_new(BIO_f_base64());

    BIO *bmem;  // read-only memory BIO

    // data is read directly from the supplied buffer: it is NOT copied first.
    // So the supplied area of memory must be unchanged until the BIO is freed.
    bmem = BIO_new_mem_buf(input, length);

    // append memory bio to base64 bio
    bmem = BIO_push(b64, bmem);

    // out buffer to be freed by the caller
    *out_buf = (char *)malloc(length);
    memset(*out_buf, 0, length);

    // By default, b64 BIO requires input to have the line terminators specified.
    // Change that with BIO_FLAGS_BASE64_NO_NL flag (no newline)
    BIO_set_flags (b64,BIO_FLAGS_BASE64_NO_NL);

    // attempts to read length bytes from bmem BIO b
    // and places the data in out_buf.
    int len_read_data = BIO_read(bmem, *out_buf, length);

    if((0 == len_read_data) || (-1 == len_read_data)){
        TRACE_WARNING("len_read_data = %d.\n", len_read_data);
        TRACE_WARNING("No data was successfully read.\n");
        rc = 1;
    }
    else {
        if(len_read_data == -2) {
            TRACE_WARNING("Operation not implemented.\n");
            rc = 2;
        }
        else {
            *len_out_buf = len_read_data;
        }
    }

    // free up the BIO chain
    BIO_free_all(bmem);

    return rc;
}
